/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.matching;

import java.util.Arrays;

import mosdi.fa.Partition;
import mosdi.paa.MinimizableDAA;
import mosdi.util.ProductEncoder;

public class PatternMatchingDAA extends MinimizableDAA {

	private AlgorithmCostScheme costScheme;
	private ProductEncoder stateEncoder;
	private ProductEncoder windowEncoder;
	private int maxTotalCost;
	
	public PatternMatchingDAA(AlgorithmCostScheme costScheme, int maxTotalCost) {
		this.costScheme = costScheme;
		this.maxTotalCost = maxTotalCost;
		int[] bases = new int[costScheme.windowSize()];
		Arrays.fill(bases, costScheme.alphabetSize());
		windowEncoder = new ProductEncoder(false,bases);
		stateEncoder = new ProductEncoder(true,costScheme.windowSize()+1,windowEncoder.getValueCount());
	}

	@Override
	public int getAlphabetSize() {
		return costScheme.alphabetSize();
	}

	/** Decodes a state into the considered search window. */
	public int[] decodeStateToWindow(int state) {
		return windowEncoder.decode(stateEncoder.decodeComponent(1, state));
	}

	/** Returns the relative position of the current search window which is encoded in
	 *  the state. The return value gives the number of characters to be read until the
	 *  current window ends.
	 */
	public int decodeStateToPosition(int state) {
		return stateEncoder.decodeComponent(0,state);
	}

	@Override
	public int getEmission(int state) {
		int pos = decodeStateToPosition(state);
		if (pos==0) {
			int[] window = decodeStateToWindow(state);
			return costScheme.windowCost(window);
		} else {
			return 0;
		}
	}

	@Override
	public int getEmissionCount() {
		return costScheme.maxWindowCost();
	}

	@Override
	public int getStartState() {
		return costScheme.windowSize();
	}

	@Override
	public int getStartValue() {
		return 0;
	}

	@Override
	public int getStateCount() {
		return stateEncoder.getValueCount();
	}

	@Override
	public int getTransitionTarget(int state, int character) {
		int[] decodedState = stateEncoder.decode(state);
		// System.out.print(String.format("state %s/%d, char: %d --> ", Arrays.toString(windowEncoder.decode(decodedState[1])), decodedState[0], character));
		if (decodedState[0]==0) {
			int[] window = windowEncoder.decode(decodedState[1]);
			decodedState[0] = costScheme.shift(window)-1;
		} else {
			decodedState[0] -= 1;
		}
		// update window
		decodedState[1] = (decodedState[1]*costScheme.alphabetSize() + character) % windowEncoder.getValueCount();
		// System.out.println(String.format("%s/%d ", Arrays.toString(windowEncoder.decode(decodedState[1])), decodedState[0]));
		return stateEncoder.encode(decodedState); 
	}

	@Override
	public int getValueCount() {
		return maxTotalCost+1;
	}

	@Override
	public int performOperation(int state, int value, int emission) {
		return Math.min(maxTotalCost, value+emission);
	}

	@Override
	public Partition getStatePartition() {
		Integer[] allEmissions = new Integer[getStateCount()];
		for (int i=0; i<getStateCount(); ++i) {
			allEmissions[i] = getEmission(i);
		}
		return new Partition(Arrays.asList(allEmissions));
	}

}
